<?php
/**
 * Main bitweaver systems functions
 *
 * @package kernel
 * @version $Header: /cvsroot/bitweaver/_bit_kernel/BitSystem.php,v 1.92 2006/08/07 07:52:33 squareing Exp $
 * @author spider <spider@steelsun.com>
 */
// +----------------------------------------------------------------------+
// | PHP version 4.??
// +----------------------------------------------------------------------+
// | Copyright (c) 2005 bitweaver.org
// +----------------------------------------------------------------------+
// | Copyright (c) 2004-2005, Christian Fowler, et. al.
// | All Rights Reserved. See copyright.txt for details and a complete list of authors.
// | Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details
// |
// | For comments, please use PEAR documentation standards!!!
// | -> see http://pear.php.net/manual/en/standards.comments.php
// |    and http://www.phpdoc.org/
// +----------------------------------------------------------------------+

/**
 * required setup
 */
require_once(KERNEL_PKG_PATH . 'BitBase.php');
require_once(KERNEL_PKG_PATH . 'BitDate.php');
require_once(KERNEL_PKG_PATH . 'BitSmarty.php');

define('DEFAULT_PACKAGE', 'kernel');
define('CENTER_COLUMN', 'c');
define('HOMEPAGE_LAYOUT', 'home');

/**
 * kernel::BitSystem
 *
 * Purpose:
 *
 *     This is the main system class that does the work of seeing Tiki has an
 * 	operable environment and has methods for modifying that environment.
 *
 * 	Currently gBitSystem derives from this class for backward compatibility sake.
 * 	Ultimate goal is to put system code from BitBase here, and base code from
 * 	gBitSystem (code that ALL features need) into BitBase and code gBitSystem that
 * 	is Package specific should be moved into that package
 *
 * @author spider <spider@steelsun.com>
 *
 * @package kernel
 */
class BitSystem extends BitBase {
	/**
	* * Array of  *
	*/
	var $mAppMenu;
	var $mPackages;
	var $mLayout;
	var $mStyle;
	var $mActivePackage;
	var $mInstallModules = array();
	/**
	* Javascript to be added to the <body onload> attribute
	* @private
	*/
	var $mOnload = array();
	// Used by packages to register notification events that can be subscribed to.
	var $mNotifyEvents = array();
	/**
	* Used to store contents of kernel_config
	* @private
	*/
	var $mConfig;
	/**
	* Used to monitor if ::registerPackage() was called. This is used to determine whether to auto-register a package
	* @private
	*/
	var $mRegisterCalled;

	var $mPackageFileName;
	
	// >>>
	// === BitSystem constructor
	/**
	* base constructor, auto assigns member db variable
	*
	* @access public
	*/
	// Constructor receiving a PEAR::Db database object.
	function BitSystem()
	{
		global $gBitTimer;
		// Call DB constructor which will create the database member variable
		BitBase::BitBase();

		$this->mAppMenu = array();
		$this->mTimer = $gBitTimer;
		$this->mServerTimestamp = new BitDate();

		$this->loadConfig();

		// Critical Preflight Checks
		$this->checkEnvironment();

		$this->initSmarty();
		$this->mRegisterCalled = FALSE;
	}
	// >>>
	// === initSmarty
	/**
	* Define and load Smarty components
	*
	* @param none $
	* @return none
	* @access private
	*/
	function initSmarty()
	{
		global $bitdomain, $_SERVER, $gBitSmarty;

		// Set the separator for PHP generated tags to be &amp; instead of &
		// This is necessary for XHTML compliance
		ini_set("arg_separator.output", "&amp;");
		// Remove automatic quotes added to POST/COOKIE by PHP
		if (get_magic_quotes_gpc())
		{
			foreach ($_REQUEST as $k => $v)
			{
				if (!is_array($_REQUEST[$k])) $_REQUEST[$k] = stripslashes($v);
			}
		}

		if (!isset($bitdomain))
		{
			$bitdomain = "";
		}

		// make sure we only create one BitSmarty
		if( !is_object( $gBitSmarty ) ) {
			$gBitSmarty = new BitSmarty();
			// set the default handler
			$gBitSmarty->load_filter('pre', 'tr');
			// $gBitSmarty->load_filter('output','trimwhitespace');
			if (isset($_REQUEST['highlight']))
			{
				$gBitSmarty->load_filter('output', 'highlight');
			}
		}
	}

	/**
	* Load all preferences and store them in $this->mConfig
	*
	* @param $pPackage optionally get preferences only for selected package
	*/
	function loadConfig($pPackage = null) {
		$queryVars = array();
		$whereClause = '';

		if ($pPackage) {
			array_push($queryVars, $pPackage);
			$whereClause = ' WHERE `package`=? ';
		}

		if ( empty( $this->mConfig ) ) {
			$this->mConfig = array();
			$query = "SELECT `config_name` ,`config_value`, `package` FROM `" . BIT_DB_PREFIX . "kernel_config` " . $whereClause;
			if( $rs = $this->mDb->query( $query, $queryVars, -1, -1 ) ) {
				while( $row = $rs->fetchRow() ) {
					$this->mConfig[$row['config_name']] = $row['config_value'];
				}
			}
		}
		return count( $this->mConfig );
	}

	// <<< getConfig
	/**
	* Add getConfig / setConfig for more uniform handling of config variables instead of spreading global vars.
	* easily get the value of any given preference stored in kernel_config
	*
	* @access public
	**/
	function getConfig( $pName, $pDefault = NULL ) {
		if( empty( $this->mConfig ) ) {
			$this->loadConfig();
		}
		return( empty( $this->mConfig[$pName] ) ? $pDefault : $this->mConfig[$pName] );
	}

	// <<< getConfigMatch
	/**
	* retreive a group of config variables
	*
	* @access public
	**/
	function getConfigMatch( $pattern, $select_value="" ) {
		if( empty( $this->mConfig ) ) {
			$this->loadConfig();
		}

		$matching_keys = array();
		$matching_keys = preg_grep($pattern, array_keys($this->mConfig));
		$new_array = array();
		foreach($matching_keys as $key=>$value) {
			if ( empty($select_value) || ( !empty($select_value) && $this->mConfig[$value] == $select_value) ) {
				$new_array[$value] = $this->mConfig[$value];
			}
		}
		return( $new_array );
	}


	/**
	* set a group of config variables
	*
	* @access public
	**/
	function setConfigMatch( $pPattern, $pSelect_value="", $pNew_value=NULL, $pPackage=NULL ) {
		if( empty( $this->mConfig ) ) {
			$this->loadConfig();
		}

		$matching_keys = array();
		$matching_keys = preg_grep($pPattern, array_keys($this->mConfig));
		$new_array = array();
		foreach($matching_keys as $key=>$config_name) {
			if ( empty($pSelect_value) || ( !empty($pSelect_value) && $this->mConfig[$config_name] == $pSelect_value) ) {
				$this->storeConfig($config_name, $pNew_value, $pPackage);
			}
		}
	}


	// deprecated method saved compatibility until all getPreference calls have been eliminated
	function getPreference( $pName, $pDefault = '' ) {
		deprecated( 'BitSystem::getConfig()' );
		return $this->getConfig( $pName, $pDefault );
	}

	/**
	* Set a hash value in the mConfig hash. This does *NOT* store the value in the database. It does no checking for existing or duplicate values. the main point of this function is to limit direct accessing of the mConfig hash. I will probably make mConfig private one day.
	*
	* @param string Hash key for the mConfig value
	* @param string Value for the mConfig hash key
	*/
	function setConfig( $pName, $pValue ) {
		$this->mConfig[$pName] = $pValue;
		return( TRUE );
	}
	function setPreference( $pPrefName, $pPrefValue ) {
		$this->setConfig( $pPrefName, $pPrefValue );
	}

	// <<< storeConfig
	/**
	* bitweaver needs lots of settings just to operate.
	* loadConfig assigns itself the default preferences, then loads just the differences from the database.
	* In storeConfig (and only when storeConfig is called) we make a second copy of defaults to see if
	* preferences you are changing is different from the default.
	* if it is the same, don't store it!
	* So instead updating the whole prefs table, only updat "delta" of the changes delta from defaults.
	*
	* @access public
	**/
	function storeConfig( $pName, $pValue, $pPackage = NULL ) {
		global $gMultisites;
		//stop undefined offset error being thrown after packages are installed
		if( !empty( $this->mConfig ) ){
			// store the pref if we have a value _AND_ it is different from the default
			if( ( empty( $this->mConfig[$pName] ) || ( $this->mConfig[$pName] != $pValue ) ) ) {
				// store the preference in multisites, if used
				if( @$this->verifyId( $gMultisites->mMultisiteId ) && isset( $gMultisites->mConfig[$pName] ) ) {
					$query = "UPDATE `".BIT_DB_PREFIX."multisite_preferences` SET `config_value`=? WHERE `multisite_id`=? AND `config_name`=?";
					$result = $this->mDb->query( $query, array( empty( $pValue ) ? '' : $pValue, $gMultisites->mMultisiteId, $pName ) );
				} else {
					$query = "DELETE FROM `".BIT_DB_PREFIX."kernel_config` WHERE `config_name`=?";
					$result = $this->mDb->query( $query, array( $pName ) );
					if( isset( $pValue ) ) {
						$query = "INSERT INTO `".BIT_DB_PREFIX."kernel_config`(`config_name`,`config_value`,`package`) VALUES (?,?,?)";
						$result = $this->mDb->query( $query, array( $pName, $pValue, strtolower( $pPackage ) ) );
					}
				}

				// Force the ADODB cache to flush
				$isCaching = $this->mDb->isCachingActive();
				$this->mDb->setCaching( FALSE );
				$this->loadConfig();
				$this->mDb->setCaching( $isCaching );
			}
		}
		$this->setConfig( $pName, $pValue );
		return TRUE;
	}
	// >>>

	// <<< expungePackageConfig
	/**
	* Delete all prefences for the given package
	* @access public
	**/
	function expungePackageConfig( $pPackageName ) {
		if( !empty( $pPackageName ) ) {
			$query = "DELETE FROM `".BIT_DB_PREFIX."kernel_config` WHERE `package`=?";
			$result = $this->mDb->query( $query, array( strtolower( $pPackageName ) ) );
			// let's force a reload of the prefs
			unset( $this->mConfig );
			$this->loadConfig();
		}
	}
	// >>>


	// >>>
	// === hasValidSenderEmail
	/**
	* Determines if this site has a legitimate sender address set.
	*
	* @param  $mid the name of the template for the page content
	* @access public
	*/
	function hasValidSenderEmail( $pSenderEmail=NULL ) {
		if( empty( $pSenderEmail ) ) {
			$pSenderEmail = $this->getConfig( 'site_sender_email' );
		}
		return( !empty( $pSenderEmail ) && !preg_match( '/.*localhost$/', $pSenderEmail ) );
	}
	// >>>

	// >>>
	// === getErrorEmail
	/**
	* Smartly determines where error emails should go
	*
	* @access public
	*/
	function getErrorEmail() {
		if( defined('ERROR_EMAIL') ) {
			$ret = ERROR_EMAIL;
		} elseif( $this->getConfig( 'site_sender_email' ) ) {
			$ret = $this->getConfig( 'site_sender_email' );
		} elseif( !empty( $_SERVER['SERVER_ADMIN'] ) ) {
			$ret = $_SERVER['SERVER_ADMIN'];
		} else {
			$ret = 'root@localhost';
		}
	}
	// >>>


	// >>>
	// === sendEmail
	/**
	* centralized function for send emails
	*
	* @param  $mid the name of the template for the page content
	* @access public
	*/
	function sendEmail( $pMailHash ) {
		$extraHeaders = '';
		if( $this->getConfig( 'bcc_email' ) ) {
			$extraHeaders = "Bcc: ".$this->getConfig( 'bcc_email' )."\r\n";
		}
		if( !empty( $pMailHash['Reply-to'] ) ) {
			$extraHeaders = "Reply-to: ".$pMailHash['Reply-to']."\r\n";
		}

		mail($pMailHash['email'],
			$pMailHash['subject'].' '.$_SERVER["SERVER_NAME"],
			$pMailHash['body'],
			"From: ".$this->getConfig( 'site_sender_email' )."\r\nContent-type: text/plain;charset=utf-8\r\n$extraHeaders"
		);
	}
	// >>>

	// >>>
	// === display
	/**
	* Display the main page template
	*
	* @param  $mid the name of the template for the page content
	* @access public
	*/
	function display( $pMid, $pBrowserTitle=NULL ) {
		global $gBitSmarty;
		$gBitSmarty->verifyCompileDir();

		header( 'Content-Type: text/html; charset=utf-8' );
		if( !empty( $pBrowserTitle ) ) {
			$this->setBrowserTitle( $pBrowserTitle );
		}
		if( $pMid == 'error.tpl' ) {
			$this->setBrowserTitle( !empty( $pBrowserTitle ) ? $pBrowserTitle : tra( 'Error' ) );
			$pMid = 'bitpackage:kernel/error.tpl';
		}

		$this->preDisplay( $pMid );
		$gBitSmarty->assign( 'mid', $pMid );
//		$gBitSmarty->assign( 'page', !empty( $_REQUEST['page'] ) ? $_REQUEST['page'] : NULL );
		// Make sure that the gBitSystem symbol available to templates is correct and up-to-date.
		$gBitSmarty->assign_by_ref('gBitSystem', $this);
		$gBitSmarty->display( 'bitpackage:kernel/bitweaver.tpl' );
		$this->postDisplay( $pMid );
	}
	// >>>
	// === preDisplay
	/**
	* Take care of any processing that needs to happen just before the template is displayed
	*
	* @param none $
	* @access private
	*/
	function preDisplay( $pMid ) {
		global $gCenterPieces, $fHomepage, $gBitSmarty, $gBitUser, $gPreviewStyle, $gQueryUser, $gQueryUserId;
		// setup our theme style and check if a preview theme has been picked
		if( $gPreviewStyle !== FALSE ) {
			$this->setStyle( $gPreviewStyle );
		}
		if( empty( $this->mStyles['styleSheet'] ) ) {
			$this->mStyles['styleSheet'] = $this->getStyleCss();
		}
		$this->mStyles['headerIncFiles'] = $this->getTplIncludeFiles("header_inc.tpl");
		$this->mStyles['footerIncFiles'] = $this->getTplIncludeFiles("footer_inc.tpl");
		$this->mStyles['browserStyleSheet'] = $this->getBrowserStyleCss();
		$this->mStyles['customStyleSheet'] = $this->getCustomStyleCss();
		//$this->mStyles['altStyleSheets'] = $this->getAltStyleCss();
		define( 'THEMES_STYLE_URL', $this->getStyleUrl() );
		define( 'JSCALENDAR_PKG_URL', UTIL_PKG_URL.'jscalendar/' );
		// dont forget to assign slideshow stylesheet if we are viewing page as slideshow
//		$gBitSmarty->assign('site_slide_style', $this->getStyleCss("site_slide_style"));

		$customHome = NULL;
		if( !empty( $gQueryUser ) && $gQueryUser->canCustomizeLayout() && @$this->verifyId( $gQueryUserId ) ) {
			$customHome = $gQueryUserId;
		} elseif( $this->getConfig( 'users_layouts' ) == 'y' ) {
			// user_layouts can have 3 falues - 'y', 'h', or 'n'/null. isFeatureActice cannot be called here.
			$customHome = $gBitUser->mUsername;
		}

		$this->loadLayout( $customHome );

		if( $pMid == 'bitpackage:kernel/dynamic.tpl' ) {
			$gBitSmarty->assign_by_ref('gCenterPieces', $gCenterPieces);
		} else {
			// we don't want to render the centers if we aren't on a dynamic page
			unset($this->mLayout['c']);
		}

		require( KERNEL_PKG_PATH . 'menu_register_inc.php' );
		require_once( KERNEL_PKG_PATH . 'modules_inc.php' );

		/* force the session to close *before* displaying. Why? Note this very important comment from http://us4.php.net/exec
			edwin at bit dot nl
			23-Jan-2002 04:47
				If you are using sessions and want to start a background process, you might
				have the following problem:
				The first time you call your script everything goes fine, but when you call it again
				and the process is STILL running, your script seems to "freeze" until you kill the
				process you started the first time.

				You'll have to call session_write_close(); to solve this problem. It has something
				to do with the fact that only one script/process can operate at once on a session.
				(others will be lockedout until the script finishes)

				I don't know why it somehow seems to be influenced by background processes,
				but I tried everything and this is the only solution. (i had a perl script that
				"daemonized" it's self like the example in the perl manuals)

				Took me a long time to figure out, thanks ian@virtisp.net! :-)

			... and a similar issue can happen for very long display times.
		*/
		session_write_close();
	}

	// >>>
	// === getTplIncludeFiles
	/**
	* scan packages for <pkg>/templates/header_inc.tpl or footer_inc.tpl files
	*
	* @param none $
	* @access private
	* @return array of paths to existing header_inc.tpl files
	*/
	function getTplIncludeFiles( $pFilename ) {
		// these package templates will be included last
		$prepend = array( 'kernel' );
		$append = array( 'themes' );
		$anti = $mid = $post = array();
		foreach( $this->mPackages as $package => $info ) {
			if( !empty( $info['path'] ) ) {
				$file = $info['path'].'templates/'.$pFilename;
				$out = "bitpackage:{$package}/{$pFilename}";
				if( is_readable( $file ) ) {
					if( in_array( $package, $prepend ) ) {
						$anti[] = $out;
					} elseif( in_array( $package, $append ) ) {
						$post[] = $out;
					} else {
						$mid[] = $out;
					}
				}
			}
		}
		$ret = array_merge( $anti, $mid, $post );
		return $ret;
	}

	// >>>
	// === postDisplay
	/**
	* Take care of any processing that needs to happen just after the template is displayed
	*
	* @param none $
	* @access private
	*/
	function postDisplay($pMid)
	{
	}
	// >>>
	// === setHelpInfo
	/**
	* Set the smarty variables needed to display the help link for a page.
	*
	* @param  $package Package Name
	* @param  $context Context of the help within the package
	* @param  $desc Description of the help link (not the help itself)
	* @access private
	*/
	function setHelpInfo($package, $context, $desc)
	{
		global $gBitSmarty;
		$gBitSmarty->assign('TikiHelpInfo', array('URL' => 'http://doc.bitweaver.org/wiki/index.php?page=' . $package . $context , 'Desc' => $desc));
	}
	// >>>

	// === isPackageActive
	/**
	* check's if a package is active.
	* @param $pPackageName the name of the package to test
	* @return none
	* @access public
	*
	* @param $pKey hash key
	*/
	function isPackageActive( $pPackageName ) {

	// A package is installed if
	//    $this->getConfig('package_'.$name) == 'i'
	// or $this->getConfig('package_'.$name) == 'y'
	//
	// A package is installed and active if
	//     <package name>_PKG_NAME is defined
	// and $this->getConfig('package_'.$name) == 'y'

		$ret = FALSE;
		if (defined( (strtoupper( $pPackageName ).'_PKG_NAME') ) ) {
			$name = strtolower( @constant( (strtoupper( $pPackageName ).'_PKG_NAME') ) );
			if( $name ) {
				// kernel always active
				if ($name == 'kernel') {
					$ret = 1;
				}
				else {
					// we have migrated the old tikiwiki feature_<pac
					$ret = ($this->getConfig('package_'.$name) == 'y');
				}

			}
		}

		return( $ret );
	}

	// === isPackageInstalled
	/**
	* check's if a package is Installed
	* @param $pPackageName the name of the package to test
	* @return none
	* @access public
	*
	* @param $pKey hash key
	*/
	function isPackageInstalled( $pPackageName ) {

	// A package is installed if
	//    $this->getConfig('package_'.$name) == 'i'
	// or $this->getConfig('package_'.$name) == 'y'
	//
	// A package is installed and active if
	//     <package name>_PKG_NAME is defined
	// and $this->getConfig('package_'.$name) == 'y'

		$ret = FALSE;
		if (defined( (strtoupper( $pPackageName ).'_PKG_NAME') ) ) {
			$name = strtolower( @constant( (strtoupper( $pPackageName ).'_PKG_NAME') ) );
			if( $name ) {
				// kernel always active
				if ($name == 'kernel') {
					$ret = 1;
				}
				else {
					// we have migrated the old tikiwiki feature_<pac
					$ret = ($this->getConfig('package_'.$name) == 'i')
					|| ($this->getConfig('package_'.$name) == 'y')
					;
				}

			}
		}

		return( $ret );
	}


	// === verifyPackage
	/**
	* It will verify that the given package is active or it will display the error template and die()
	* @param $pPackageName the name of the package to test
	* @return none
	*
	* @param  $pKey hash key
	* @access public
	*/
	function verifyPackage( $pPackageName )
	{
		if( !$this->isPackageActive( $pPackageName ) ) {
			$this->fatalError( tra("This package is disabled").": package_$pPackageName" );
		}

		return( TRUE );
	}


	// === getPermissionInfo
	/**
	* It will get information about a permissions
	* @param $pPermission value of a given permission
	* @return none
	* @access public
	*/
	function getPermissionInfo( $pPermission = NULL, $pPackageName = NULL ) {
		$ret = NULL;
		$bindVars = array();
		$sql = 'SELECT * FROM `'.BIT_DB_PREFIX.'users_permissions` ';
		if( !empty( $pPermission ) ) {
			$sql .= ' WHERE `perm_name`=? ';
			array_push( $bindVars, $pPermission );
		} elseif ( !empty( $pPackageName ) ) {
			$sql .= ' WHERE `package` = ? ';
			array_push( $bindVars, substr($pPackageName,0,100));
		}
		$ret = $this->mDb->getAssoc( $sql, $bindVars );
		return $ret;
	}

	// === verifyPermission
	/**
	* This code was duplicated _EVERYWHERE_ so here is an easy template to cut that down.
	* It will verify if a given user has a given $permission and if not, it will display the error template and die()
	* @param $pPermission value of a given permission
	* @return none
	* @access public
	*/
	function verifyPermission( $pPermission, $pMsg = NULL ) {
		global $gBitSmarty, $gBitUser, ${$pPermission};
		if( empty( $pPermission ) || $gBitUser->hasPermission( $pPermission ) ) {
			return TRUE;
		} else {
			$this->fatalPermission( $pPermission, $pMsg );
		}
	}

	function fatalPermission( $pPermission, $pMsg=NULL ) {
		global $gBitUser, $gBitSmarty;
		if( empty( $pMsg ) ) {
			$permDesc = $this->getPermissionInfo( $pPermission );
			$pMsg = "You do not have the required permissions ";
			if( !empty( $permDesc[$pPermission]['perm_desc'] ) ) {
				if( preg_match( '/administrator,/i', $permDesc[$pPermission]['perm_desc'] ) ) {
					$pMsg .= preg_replace( '/^administrator, can/i', ' to ', $permDesc[$pPermission]['perm_desc'] );
				} else {
					$pMsg .= preg_replace( '/^can /i', ' to ', $permDesc[$pPermission]['perm_desc'] );
				}
			}
		}
		if( !$gBitUser->isRegistered() ) {
			$pMsg .= '</p><p>You must be logged in. Please <a href="'.USERS_PKG_URL.'login.php">login</a> or <a href="'.USERS_PKG_URL.'register.php">register</a>.';
			$gBitSmarty->assign( 'template', 'bitpackage:users/login_inc.tpl' );
		}
		$gBitSmarty->assign( 'fatalTitle', tra( "Permission denied." ) );
		$gBitSmarty->assign( 'msg', tra( $pMsg ) );
		$this->display( "error.tpl" );
		die;
	}

	/**
	* This code was duplicated _EVERYWHERE_ so here is an easy template to cut that down.
	* It will verify if a given user has a given $permission and if not, it will display the error template and die()
	* @param $pPermission value of a given permission
	* @return none
	* @access public
	*/
	function confirmDialog( $pFormHash, $pMsg )
	{
		global $gBitSmarty;
		if( !empty( $pMsg ) ) {
			if( empty( $pParamHash['cancel_url'] ) ) {
				$gBitSmarty->assign( 'backJavascript', 'onclick="history.back();"' );
			}
			if( !empty( $pFormHash['input'] ) ) {
				$gBitSmarty->assign( 'inputFields', $pFormHash['input'] );
				unset( $pFormHash['input'] );
			}
			$gBitSmarty->assign( 'msgFields', $pMsg );
			$gBitSmarty->assign_by_ref( 'hiddenFields', $pFormHash );
			$this->display( 'bitpackage:kernel/confirm.tpl' );
			die;
		}
	}

	// === isFeatureActive
	/**
	* check's if the specfied feature is active
	*
	* @param  $pKey hash key
	* @return none
	* @access public
	*/
	function isFeatureActive( $pFeatureName )
	{
		$ret = FALSE;
		if( $pFeatureName ) {
			$featureValue = $this->getConfig($pFeatureName);
			$ret = !empty( $featureValue ) && ( $featureValue != 'n' );
		}

		return( $ret );
	}


	// === verifyFeature
	/**
	* It will verify that the given feature is active or it will display the error template and die()
	* @param $pFeatureName the name of the package to test
	* @return none
	* @access public
	*
	* @param  $pKey hash key
	*/
	function verifyFeature( $pFeatureName )
	{
		if( !$this->isFeatureActive( $pFeatureName ) ) {
			$this->fatalError( tra("This feature is disabled").": $pFeatureName" );
		}

		return( TRUE );
	}


	// === registerPackage
	/**
	* Define name, location and url DEFINE's
	*
	* @param  $pKey hash key
	* @return none
	* @access public
	*/
	function registerPackage( $pRegisterHash ) {
		extract( $pRegisterHash );
		#extract can set:
		#  $package_name
		#  $package_path
		#  $activatable
		#  $service
		#  $required_package

		if( !isset( $activatable ) ) {
			$activatable = TRUE;
		}

		if( !isset( $service ) ) {
			$service = FALSE;
		}

		if( !isset( $required_package ) ) {
			$required_package = FALSE;
		}

		if( !isset( $package_name ) ) {
			$this->fatalError( tra("Package name not set in ")."registerPackage: $this->mPackageFileName" );;
		}

		if( !isset( $package_path ) ) {
			$this->fatalError( tra("Package path not set in ")."registerPackage: $this->mPackageFileName" );;
		}


		$this->mRegisterCalled = TRUE;
		if( empty( $this->mPackages ) ) {
			$this->mPackages = array();
		}
		$pkgName = str_replace( ' ', '_', strtoupper( $package_name ) );
		$pkgNameKey = strtolower( $pkgName );

		// Set package required flag
		$this->mPackages[$pkgNameKey]['required']  = $required_package;

		// indicate if this package is a service or not
		$this->mPackages[$pkgNameKey]['service'] = $service;

		// set package status flag
		$this->mPackages[$pkgNameKey]['status'] = $this->getConfig( 'package_'.$pkgNameKey, 'n');
		# y = Active
		# i = Installed
		# n (or empty/null) = Not Active and Not Installed

		// set package installed and active flag
		if ($this->mPackages[$pkgNameKey]['status'] == 'a' || $this->mPackages[$pkgNameKey]['status'] == 'y') {
			$this->mPackages[$pkgNameKey]['active_switch'] = TRUE;
			}
		else {
			$this->mPackages[$pkgNameKey]['active_switch'] = FALSE;
			}

		// set package installed flag (can be installed but not active)
		if ($this->mPackages[$pkgNameKey]['active_switch'] || $this->mPackages[$pkgNameKey]['status'] == 'i') {
			$this->mPackages[$pkgNameKey]['installed'] = TRUE;
			}
		else {
			$this->mPackages[$pkgNameKey]['installed'] = FALSE;
			}

		// Define <PACKAGE>_PKG_PATH
		$pkgDefine = $pkgName.'_PKG_PATH';
		if (!defined($pkgDefine)) {
			define($pkgDefine, $package_path);
		}
		$this->mPackages[$pkgNameKey]['url']  = BIT_ROOT_URL . basename( $package_path ) . '/';
		$this->mPackages[$pkgNameKey]['path']  = BIT_ROOT_PATH . basename( $package_path ) . '/';

		// Define <PACKAGE>_PKG_URL
		$pkgDefine = $pkgName.'_PKG_URL';
		if (!defined($pkgDefine)) {
			define($pkgDefine, BIT_ROOT_URL . basename( $package_path ) . '/');
		}

		// Define <PACKAGE>_PKG_URI
		$pkgDefine = $pkgName.'_PKG_URI';
		if (!defined($pkgDefine) && defined( 'BIT_BASE_URI' ) ) {
			define($pkgDefine, BIT_BASE_URI . '/' . basename( $package_path ) . '/');
		}

		// Define <PACKAGE>_PKG_NAME
		$pkgDefine = $pkgName.'_PKG_NAME';
		if (!defined($pkgDefine)) {
			define($pkgDefine, $package_name);
			$this->mPackages[$pkgNameKey]['activatable'] = $activatable;
		}
		$this->mPackages[$pkgNameKey]['name'] = $package_name;

		// Define <PACKAGE>_PKG_DIR
		$pkgDefine = $pkgName.'_PKG_DIR';
		if (!defined($pkgDefine)) {
			define($pkgDefine, basename( $package_path ));
		}
		$this->mPackages[$pkgNameKey]['dir'] = basename( $package_path );

		// Define the package we are currently in
		// I tried strpos instead of preg_match here, but it didn't like strings that begin with slash?! - spiderr
		if( !defined('ACTIVE_PACKAGE') && (isset($_SERVER['ACTIVE_PACKAGE'] ) || preg_match( '/\/'.$this->mPackages[$pkgNameKey]['dir'].'\//', $_SERVER['PHP_SELF'] ) || preg_match( '/\/' . $package_name . '\//', $_SERVER['PHP_SELF'] )) ) {
			if( isset($_SERVER['ACTIVE_PACKAGE'] ) ) {
				// perhaps the webserver told us the active package (probably because of mod_rewrites)
				$package_name = $_SERVER['ACTIVE_PACKAGE'];
			}
			define('ACTIVE_PACKAGE', $package_name);
			define('ACTIVE_PACKAGE_DIR', $this->mPackages[$pkgNameKey]['dir'] );
			$this->mActivePackage = $package_name;
		}

	}
	// >>>
	// === registerAppMenu
	/**
	* Define and load Smarty components
	*
	* @param  $pKey hash key
	* @return none
	* @access public
	*/
	function registerAppMenu( $pKey, $pMenuTitle, $pTitleUrl, $pMenuTemplate, $pAdminPanel = false ) {
		$this->mAppMenu[strtolower( $pKey )] = array(
			'title' => $pMenuTitle,
			'disabled' => ( $this->getConfig( 'menu_'.$pKey ) == 'n' ),
			'titleUrl' => $pTitleUrl,
			'template' => $pMenuTemplate,
			'adminPanel' => $pAdminPanel,
			'style' => 'display:'.( empty( $pMenuTitle ) || ( isset( $_COOKIE[$pKey.'menu'] ) && ( $_COOKIE[$pKey.'menu'] == 'o' ) ) ? 'block;' : 'none;' )
			// TODO this display logic should maybe be moved to .tpl logic, but need to acces $_COOKIES in {$smartVar}
		);
	}
	// >>>
	// === registerSchemaTable
	/**
	* "Virtual" function stub - fully defined in BitInstaller
	*
	* @return none
	* @access public
	*/
	function registerSchemaTable( $pPackage, $pTableName, $pDataDict, $pRequired=FALSE, $pTableOptions=NULL ) {
		$pPackage = strtolower( $pPackage ); // lower case for uniformity
		if( !empty( $pTableName ) ) {
			$this->mPackages[$pPackage]['tables'][$pTableName] = $pDataDict;
			if( !empty( $pTableOptions ) ) {
				$this->mPackages[$pPackage]['tables']['options'][$pTableName] = $pTableOptions;
			}
			// Package required now set in register package function
			// A package is required if _any_ of it's tables are required
			//$this->mPackages[$pPackage]['required'] = $pRequired | (isset( $this->mPackages[$pPackage]['required'] ) ? $this->mPackages[$pPackage]['required'] : 0 );
		}
	}
	// >>>
	// === registerPackageInfo
	/**
	* "Virtual" function stub - fully defined in BitInstaller
	*
	* @return none
	* @access public
	*/
	function registerPackageInfo( $pPackage, $pInfoHash ) {
		$pPackage = strtolower( $pPackage ); // lower case for uniformity
		$this->mPackages[$pPackage]['info'] = $pInfoHash;
	}
	// >>>
	// === registerSchemaSequences
	/**
	* accepts a sequence to be added to the install list
	*
	* @return none
	* @access public
	*/
	function registerSchemaSequences( $pPackage, $pSeqHash ) {
		$pPackage = strtolower( $pPackage ); // lower case for uniformity
		$this->mPackages[$pPackage]['sequences'] = $pSeqHash;
	}
	// >>>
	// >>>
	// === registerSchemaIndex
	/**
	* "Virtual" function stub - fully defined in BitInstaller
	*
	* @return none
	* @access public
	*/
	function registerSchemaIndexes( $pPackage, $pIndexHash ) {
		$pPackage = strtolower( $pPackage ); // lower case for uniformity
		$this->mPackages[$pPackage]['indexes'] = $pIndexHash;
	}
	// >>>
	// === registerSchemaDefault
	/**
	* "Virtual" function stub - fully defined in BitInstaller
	*
	* @return none
	* @access public
	*/
	function registerSchemaDefault( $pPackage, $pMixedDefaultSql ) {
		$pPackage = strtolower( $pPackage ); // lower case for uniformity
		if( empty( $this->mPackages[$pPackage]['defaults'] ) ) {
			$this->mPackages[$pPackage]['defaults'] = array();
		}
		if( is_array( $pMixedDefaultSql ) ) {
			foreach( $pMixedDefaultSql as $def ) {
				$this->mPackages[$pPackage]['defaults'][] = $def;
			}
		} elseif( is_string( $pMixedDefaultSql ) ) {
			array_push( $this->mPackages[$pPackage]['defaults'], $pMixedDefaultSql );
		}
	}

	/**
	* registerSchemaTable - Handles big array of update info
	*
	* @return none
	* @access public
	*/
	function registerUpgrade( $pPackage, $pUpgradeHash ) {
		$pPackage = strtolower( $pPackage ); // lower case for uniformity
		if( !empty( $pUpgradeHash ) ) {
			$this->mUpgrades[$pPackage] = $pUpgradeHash;
		}
	}

	/**
	* Wrap registerSchemaDefault to handle an array of defaults
	*
	* @return none
	* @access public
	*/
//	function registerMenuOptions( $packagedir, $menu_options ) {
//		foreach( $menu_options as $opt ) {
//			$this->registerSchemaDefault( $packagedir,
//			"INSERT INTO `".BIT_DB_PREFIX."tiki_menu_options` (`menu_id` , `type` , `name` , `url` , `position` , `section` , `perm` , `groupname`) VALUES ($opt[0],'$opt[1]','$opt[2]','$opt[3]',$opt[4],'$opt[5]','$opt[6]','$opt[7]')");
//		}
//	}

	function registerUserPermissions( $packagedir, $userpermissions ) {
		foreach( $userpermissions as $perm ) {
			$this->mPermHash[$perm[0]] = $perm;
			$this->mPermHash[$perm[0]]['sql'] = "INSERT INTO `".BIT_DB_PREFIX."users_permissions` (`perm_name`, `perm_desc`, `perm_level`, `package`) VALUES ('$perm[0]', '$perm[1]', '$perm[2]', '$perm[3]')";
			$this->registerSchemaDefault( $packagedir,
			"INSERT INTO `".BIT_DB_PREFIX."users_permissions` (`perm_name`, `perm_desc`, `perm_level`, `package`) VALUES ('$perm[0]', '$perm[1]', '$perm[2]', '$perm[3]')");
		}
	}

	function registerConfig( $packagedir, $preferences ) {
		foreach( $preferences as $pref ) {
			$this->registerSchemaDefault( $packagedir,
			"INSERT INTO `".BIT_DB_PREFIX."kernel_config`(`package`,`config_name`,`config_value`) VALUES ('$pref[0]', '$pref[1]','$pref[2]')");
		}
	}
	function registerPreferences( $packagedir, $preferences ) {
		$this->registerConfig( $packagedir, $preferences );
	}

	function registerModules( $pModuleHash ) {
		$this->mInstallModules = array_merge( $this->mInstallModules, $pModuleHash );
	}

	function registerNotifyEvent( $pEventHash ) {
		$this->mNotifyEvents = array_merge( $this->mNotifyEvents, $pEventHash );
	}

	// >>>
	// === fatalError
	/**
	* If an unrecoverable error has occurred, this method should be invoked. script exist occurs
	*
	* @param string $ pMsg error message to be displayed
	* @return none this function will DIE DIE DIE!!!
	* @access public
	*/
	function fatalError( $pMsg, $pTemplate='error.tpl', $pErrorTitle="Seems there's been a problem." )
	{
		global $gBitSmarty;
		$gBitSmarty->assign('fatalTitle', tra($pErrorTitle) );
		$gBitSmarty->assign('msg', tra($pMsg) );
		$this->display( $pTemplate );
		die;
	}

	// >>>
	// === loadPackage
	/**
	* Loads a package
	*
	* @param string $ pkgName = package name to load
	* @param string $ pScanFile file to be looked for
	* @param string $ autoRegister - TRUE = autoregister any packages that don't register on their own, FALSE = don't
	* @param string $ pOnce - TRUE = do include_once to load file FALSE = do include to load the file
	* @return
	* @access public
	*/
	function loadPackage ($pkgName, $pScanFile, $autoRegister=TRUE, $pOnce=TRUE)
	{

		#check if already loaded, loading again won't work with 'include_once' since
		#no register call will be done, so don't auto register.
		if ($autoRegister && !empty($this->mPackages[$pkgName]['name'])) {
			$autoRegister = FALSE;
			}

		$this->mRegisterCalled = FALSE;
		$scanFile = BIT_ROOT_PATH.$pkgName.'/'.$pScanFile;
		$file_exists = 0;
		if (file_exists( $scanFile )) {
			$file_exists = 1;
			global $gBitSystem, $gLibertySystem, $gBitSmarty, $gBitUser, $gBitLanguage;
			$this->mPackageFileName = $scanFile;
			if( $pOnce ) {
				include_once( $scanFile );
			} else {
				include( $scanFile );
			}
		}

		if( ($file_exists || $pkgName == 'kernel') && ($autoRegister && !$this->mRegisterCalled) ) {
			$registerHash = array(
				'package_name' => $pkgName,
				'package_path' => BIT_ROOT_PATH.$pkgName.'/',
				'activatable' => FALSE,
			);
			if ($pkgName == 'kernel') {
				$registerHash = array_merge($registerHash, array('required_package'=>TRUE));
				}
			$this->registerPackage( $registerHash );
		}

	}

	// >>>
	// === scanPackages
	/**
	* scan all available packages. This is an *expensive* function. DO NOT call this functionally regularly , or arbitrarily. Failure to comply is punishable by death by jello suffication!
	*
	* @param string $ pScanFile file to be looked for
	* @param string $ pOnce - TRUE = do include_once to load file FALSE = do include to load the file
	* @param string $ pSelect - empty or 'all' = load all packages, 'installed' = load installed, 'active' = load active, 'x' = load packages with status x
	* @param string $ autoRegister - TRUE = autoregister any packages that don't register on their own, FALSE = don't
	* @param string $ fileSystemScan - TRUE = scan file system for packages to load, False = don't
	* @return none
	* @access public
	*/
	function scanPackages( $pScanFile = 'bit_setup_inc.php', $pOnce=TRUE, $pSelect='', $autoRegister=TRUE, $fileSystemScan=TRUE ) {

		global $gPreScan;

		#get list of packages from DB
		$packages_config_array = $this->getConfigMatch("/^package_/i");

		$packages_to_scan = array();
		if (!empty($gPreScan) && is_array($gPreScan)) {
			# gPreScan may hold a list of packages that must be loaded first
			$packages_to_scan = array_flip($gPreScan);
			}

		foreach ($packages_config_array as $package_name=>$config_setting) {
			$work = $package_name;
			$work = preg_replace( "/^package_/", '', $work,1 );
			# ingore if already in list
			if (!empty($packages_to_scan[$work])) {
				continue;
				}
			# add to list
			if ( ( !empty($pSelect) && $config_setting == $pSelect )
			  || ( !empty($pSelect) && $pSelect == 'all' )
			  || ( !empty($pSelect) && $pSelect == 'installed' && ( $config_setting == 'y' || $config_setting == 'i' ) )
			  || ( !empty($pSelect) && $pSelect == 'active' && ( $config_setting == 'y' ) )
			  || empty($pSelect) ) {
				$packages_to_scan[$work] = 1;
				}
			}

		#load the specified packages
		foreach(array_keys($packages_to_scan) as $pkgName) {
			$this->loadPackage($pkgName, $pScanFile, $autoRegister, $pOnce);
		}

		if ($fileSystemScan) {
			// load lib configs
			if( $pkgDir = opendir(BIT_ROOT_PATH) ) {
				while (false !== ($dirName = readdir($pkgDir))) {
					if (is_dir(BIT_ROOT_PATH . '/' . $dirName) && ($dirName != 'CVS') && ( preg_match( '/^\w/', $dirName)) ) {
						if (!empty($packages_to_scan[$dirName])) {
							continue;
							}
						$scanFile = BIT_ROOT_PATH.$dirName.'/'.$pScanFile;
						$this->loadPackage($dirName, $pScanFile, $autoRegister, $pOnce);
					}
				}

			}

		}

		#in case some defines not done
		if (!defined('ACTIVE_PACKAGE')) {
			define('ACTIVE_PACKAGE', 'kernel'); // when in doubt, assume the kernel
		}

		if( !defined( 'BIT_STYLES_PATH' ) && defined( 'THEMES_PKG_PATH' ) ) {
			define('BIT_STYLES_PATH', THEMES_PKG_PATH . 'styles/');
		}
		if( !defined( 'BIT_STYLES_URL' ) && defined( 'THEMES_PKG_URL' ) ) {
			define('BIT_STYLES_URL', THEMES_PKG_URL . 'styles/');
		}

		asort( $this->mAppMenu );
	}

	// >>>
	// === verifyInstalledPackages
	/**
	* scan all available packages
	*
	* @param string $ pScanFile file to be looked for
	* @return none
	* @access public
	*/
	function verifyInstalledPackages($pSelect='installed',$fileSystemScan=FALSE) {
		global $gBitDbType;
		#load in any admin/schema_inc.php files that exist for each package
		$this->scanPackages( 'admin/schema_inc.php',TRUE,$pSelect,FALSE,$fileSystemScan);

		if( $this->isDatabaseValid() ) {
			if (strlen(BIT_DB_PREFIX) > 0) {
				$lastQuote = strrpos( BIT_DB_PREFIX, '`' );
				if( $lastQuote != FALSE ) {
					$lastQuote++;
				}
				$prefix = substr( BIT_DB_PREFIX,  $lastQuote );
			} else {
				$prefix = '';
			}
			$showTables = ( $prefix ? $prefix.'%' : NULL );
			if( $dbTables = $this->mDb->MetaTables('TABLES', FALSE, $showTables ) ) {
				foreach( array_keys( $this->mPackages ) as $package ) {
					if( !empty( $this->mPackages[$package]['tables'] ) ) {
						foreach( array_keys( $this->mPackages[$package]['tables'] ) as $table ) {
							// painful hardcoded exception for bitcommerce
							if( $package == 'bitcommerce' ) {
								$fullTable = $table;
							} else {
								$fullTable = $prefix.$table;
							}
							$tablePresent = in_array( $fullTable, $dbTables );
							if( !$tablePresent ) {
								// There is an incomplete table
								//	vd( "Missing Table: $fullTable" );
							}
/*
							if( isset( $this->mPackages[$package]['installed'] ) ) {
								$this->mPackages[$package]['installed'] &= $tablePresent;
							} else {
								$this->mPackages[$package]['installed'] = $tablePresent;
							}
*/
							if( isset( $this->mPackages[$package]['db_tables_found'] ) ) {
								$this->mPackages[$package]['db_tables_found'] &= $tablePresent;
							} else {
								$this->mPackages[$package]['db_tables_found'] = $tablePresent;
							}
						}
					} else {
						$this->mPackages[$package]['db_tables_found'] = FALSE;
					}

					$this->mPackages[$package]['active_switch'] = $this->getConfig( 'package_'.strtolower( $package ) );
					if( !empty( $this->mPackages[$package]['required'] ) && $this->mPackages[$package]['active_switch'] != 'y' ) {
						// we have a disabled required package. turn it back on!
						$this->storeConfig( 'package_'.strtolower( $package ), 'y', $package );
						$this->mPackages[$package]['active_switch'] = $this->getConfig( 'package_'.strtolower( $package ) );
					}
				}
			}

//not clear what to do with this yet
/*
			foreach( array_keys( $this->mPackages ) as $package ) {
				if (!empty( $this->mPackages[$package]['installed'] ) && $this->getConfig("package_".strtolower($package)) != 'y') {
					$this->storeConfig('package_'.strtolower( $package ), 'n', $package);
				} elseif( empty( $this->mPackages[$package]['installed'] ) ) {
					// Delete the package_<pkgname> row from kernel_config
					$this->storeConfig('package_'.strtolower( $package ), NULL );
				}
			}
*/
		}

	}



	// Allows a package to be selected as the homepage for the site (Admin->General Settings)
	// Calls to this function should be made from each 'homeable' package's schema_inc.php
	function makePackageHomeable($package) {
		$this->mPackages[strtolower( $package )]['homeable'] = TRUE;
	}

	function getDefaultPage() {
		global $userlib, $gBitUser, $gBitSystem;
		$bit_index = $this->getConfig( "bit_index" );
		$url = '';
		if ( $bit_index == 'group_home') {
			// See if we have first a user assigned default group id, and second a group default system preference
			if( @$this->verifyId( $gBitUser->mInfo['default_group_id'] ) && ( $group_home = $gBitUser->getGroupHome( $gBitUser->mInfo['default_group_id'] ) ) ) {
			} elseif( $this->getConfig( 'default_home_group' ) && ( $group_home = $gBitUser->getGroupHome( $this->getConfig( 'default_home_group' ) ) ) ) {
			}

			if( !empty( $group_home ) ) {
				if( $this->verifyId( $group_home ) ) {
					$url = BIT_ROOT_URL."index.php".( !empty( $group_home ) ? "?content_id=".$group_home : "" );
				} elseif( strpos( $group_home, '/' ) === FALSE ) {
					$url = BitPage::getDisplayUrl( $group_home );
				} else {
					$url = $group_home;
				}
			}
		} elseif( $bit_index == 'my_page' || $bit_index == 'my_home' || $bit_index == 'user_home'  ) {
			// TODO: my_home is deprecated, but was the default for BWR1. remove in DILLINGER - spiderr
			if( $gBitUser->isRegistered() ) {
				if( !$gBitUser->isRegistered() ) {
					$url = USERS_PKG_URL.'login.php';
				} else {
					if( $bit_index == 'my_page' ) {
						$url = USERS_PKG_URL . 'my.php';
					} elseif( $bit_index == 'user_home' ) {
						$url = $gBitUser->getDisplayUrl();
					} else {
						$users_homepage = $gBitUser->getPreference( 'users_homepage' );
						if (isset($users_homepage) && !empty($users_homepage)) {
							if (strpos($users_homepage, '/') === false) {
								$url = BitPage::getDisplayUrl( $users_homepage );
							} else {
								$url = $users_homepage;
							}
						}
					}
				}
			} else {
				$url = USERS_PKG_URL . 'login.php';
			}
		} elseif( in_array( $bit_index, array_keys( $gBitSystem->mPackages ) ) ) {
			$work = strtoupper( $bit_index ).'_PKG_URL';
			if (defined("$work")) {
				$url = constant( $work );
			}

//this sends requests to inactive packages so commented out
//for example if wiki is made not active, we can end up trying to go there
//		} elseif( !empty( $bit_index ) ) {
//			$url = BIT_ROOT_URL.$bit_index;
		}

		// if no special case was matched above, default to users' my page
		if( empty( $url ) ) {
			if( $this->isPackageActive( 'wiki' ) ) {
				$url = WIKI_PKG_URL;
			} elseif( !$gBitUser->isRegistered() ) {
				$url = USERS_PKG_URL . 'login.php';
			} else {
				$url = USERS_PKG_URL . 'my.php';
			}
		}
		return $url;
	}
	// === setOnloadScript
	/**
	* set the title of the browser
	*
	* @param string $ pScanFile file to be looked for
	* @return none
	* @access public
	*/
	function setOnloadScript( $pJavscript )
	{
		array_push( $this->mOnload, $pJavscript );
	}
	// === getStyle
	/**
	* figure out the current style
	*
	* @param string $ pScanFile file to be looked for
	* @return none
	* @access public
	*/
	function getStyle()
	{
		if (empty($this->mStyle))
		{
			$this->mStyle = $this->getConfig('style');
		}
		return $this->mStyle;
	}
	// === setStyle
	/**
	* figure out the current style
	*
	* @param string $ pScanFile file to be looked for
	* @return none
	* @access public
	*/
	function setStyle($pStyle)
	{
		global $gBitSmarty;
		$this->mStyle = $pStyle;
		$gBitSmarty->assign( 'style', $pStyle );
	}
	// === setBrowserTitle
	/**
	* set the title of the browser
	*
	* @param string $ pScanFile file to be looked for
	* @return none
	* @access public
	*/
	function getBrowserTitle()
	{
		global $gPageTitle;
		return( $gPageTitle );
	}
	// === setBrowserTitle
	/**
	* set the title of the browser
	*
	* @param string $ pScanFile file to be looked for
	* @return none
	* @access public
	*/
	function setBrowserTitle($pTitle)
	{
		global $gBitSmarty, $gPageTitle;
		$gPageTitle = $pTitle;
		$gBitSmarty->assign('browserTitle', $pTitle);
		$gBitSmarty->assign('gPageTitle', $pTitle);
	}
	// === getStyleCss
	/**
	* figure out the current style
	*
	* @param string $ pScanFile file to be looked for
	* @return none
	* @access public
	*/
	function getStyleCss($pStyle = NULL, $pUserId = NULL) {
		global $gBitUser, $gBitSystem;
		if( empty( $pStyle ) ) {
			$pStyle = $this->getStyle();
		}
		$ret = '';
		if( $pStyle == 'custom' ) {
			// This is a page which uses a user-customized theme
			// The user who owns the page (whose custom theme is being requested)
			$homepageUser = new BitUser($pUserId);
			$homepageUser->load();
			// Path to the user-customized css file
			$cssPath = $homepageUser->getStoragePath( 'theme', $homepageUser->mUserId, NULL ).'custom.css';
			if( file_exists( $cssPath ) ) {
				$ret = $homepageUser->getStorageURL( 'theme', $homepageUser->mUserId, NULL ).'custom.css';
			}
		} else {
			if( $gBitSystem->getConfig( 'style_variation' ) && is_readable( THEMES_PKG_PATH.'styles/'.$pStyle.'/alternate/'.$gBitSystem->getConfig( 'style_variation' ).'.css' ) ) {
				$ret = THEMES_PKG_URL.'styles/'.$pStyle.'/alternate/'.$gBitSystem->getConfig( 'style_variation' ).'.css';
			} elseif( is_readable( THEMES_PKG_PATH.'styles/'.$pStyle.'/'.$pStyle.'.css' ) ) {
				$ret = THEMES_PKG_URL.'styles/'.$pStyle.'/'.$pStyle.'.css';
			}
		}
		return $ret;
	}
	// === getCustomStyleCss
	/**
	* get the users custom.css file if there is one
	*
	* @param pStyle style the custom.css is part of
	* @return path to custom.css file
	* @access public
	*/
	function getCustomStyleCss( $pStyle = null ) {
		$ret = null;
		if( empty( $pStyle ) ) {
			$pStyle = $this->getStyle();
		}
		return $ret;
	}
	// >>>
	// === getBrowserStyleCss
	/**
	* get browser specific css file
	*
	* @param none
	* @return path to browser specific css file
	* @access public
	*/
	function getBrowserStyleCss() {
		global $gSniffer;
		if( file_exists( $this->getStylePath().$this->getStyle().'_'.$gSniffer->property( 'browser' ).'.css' ) ) {
			$ret = $this->getStyleUrl().$this->getStyle().'_'.$gSniffer->property( 'browser' ).'.css';
		}
		return !empty( $ret ) ? $ret : NULL;
	}
	// >>>
	// === getAltStyleCss
	/**
	* get alternate style sheets
	*
	* @param none
	* @return array of style sheets with name of stylesheet as array
	* @access public
	*/
	function getAltStyleCss() {
		$ret = NULL;
		$alt_path = $this->getStylePath().'alternate/';
		$alt_url = $this->getStyleUrl().'alternate/';
		if( is_dir( $alt_path ) && $handle = opendir( $alt_path ) ) {
			while( FALSE !== ( $file = readdir( $handle ) ) ) {
				if( ( $file != '.' || $file != '..' ) && preg_match( "/\.css$/i", $file ) ) {
					$p[0] = "/_/";
					$r[0] = " ";
					$p[1] = "/\.css$/i";
					$r[1] = "";
					$name = preg_replace( $p, $r, $file );
					$ret[$name] = $alt_url.$file;
				}
			}
			closedir( $handle );
		}
		return $ret;
	}
	// >>>
	// === getStyleUrl
	/**
	* figure out the current style URL
	*
	* @param string $ pScanFile file to be looked for
	* @return none
	* @access public
	*/
	function getStyleUrl($pStyle = null)
	{
		if (empty($pStyle))
		{
			$pStyle = $this->getStyle();
		}
		return THEMES_PKG_URL . 'styles/' . $pStyle . '/';
	}
	// >>>
	// === getStylePath
	/**
	* figure out the current style URL
	*
	* @param string $ pScanFile file to be looked for
	* @return none
	* @access public
	*/
	function getStylePath($pStyle = null)
	{
		if (empty($pStyle))
		{
			$pStyle = $this->getStyle();
		}
		return THEMES_PKG_PATH . 'styles/' . $pStyle . '/';
	}
	// >>>
	// === loadModules
	/**
	* load all modules. LOAD functions imply getting data from database and putting in local member variable
	*
	* @param  $pLayout can be either the username for user assigned modules, or
	* @return none
	* @access public
	*/
	function loadLayout($pUserMixed = ROOT_USER_ID, $pLayout = ACTIVE_PACKAGE, $pFallbackLayout = DEFAULT_PACKAGE, $pForceReload = false)
	{
		if( $pForceReload || empty($this->mLayout) || !count($this->mLayout) ){
			unset($this->mLayout);
			$this->mLayout = $this->getLayout($pUserMixed, $pLayout, $pLayout, $pFallbackLayout);
		}
	}

	function getLayout($pUserMixed = null, $pLayout = ACTIVE_PACKAGE, $pFallback = true, $pFallbackLayout = DEFAULT_PACKAGE)
	{
		global $gCenterPieces, $gBitUser;
		$ret = array( 'l' => NULL, 'c' => NULL, 'r' => NULL );
		$layoutUserId = ROOT_USER_ID;

		if( !empty($pUserMixed) ) {
			if( $this->verifyId( $pUserMixed ) ) {
				$whereClause = " WHERE tl.`user_id`=?";
			} else {
				$whereClause = ", `" . BIT_DB_PREFIX . "users_users` uu WHERE tl.`user_id`=uu.`user_id` AND uu.`login`=?";
			}
			$query = "SELECT tl.`user_id` FROM `" . BIT_DB_PREFIX . "themes_layouts` tl
					$whereClause ";
			if( $userId = $this->mDb->getOne($query, array($pUserMixed)) ) {
				$layoutUserId = $userId;
			}
		}
		$whereClause = 'AND ';
		$bindVars = array($layoutUserId);
		// This query will always pull ALL of the ACTIVE_PACKAGE _and_ DEFAULT_PACKAGE modules (in that order)
		// This saves a count() query to see if the ACTIVE_PACKAGE has a layout, since it usually probably doesn't
		// I don't know if it's better or not to save the count() query and retrieve more data - my gut says so,
		// but i've done no research - spiderr
		if ($pLayout != DEFAULT_PACKAGE && $pFallback && $this->dType != 'firebird' && $this->dType != 'mssql'  && $this->dType != 'oci8'  && $this->dType != 'oci8po' ) {
			// ORDER BY comparison is crucial so current layout modules come up first
			$whereClause .= " (tl.`layout`=? OR tl.`layout`=? ) ORDER BY tl.`layout`=? DESC, ";
			array_push($bindVars, $pLayout);
			array_push($bindVars, $pFallbackLayout);
			array_push($bindVars, $pLayout);
		} elseif ($pLayout != DEFAULT_PACKAGE && $pFallback) {
			// ORDER BY comparison is crucial so current layout modules come up first
			$whereClause .= " (tl.`layout`=? OR tl.`layout`=? ) ORDER BY tl.`layout` DESC, ";
			array_push($bindVars, $pLayout);
			array_push($bindVars, $pFallbackLayout);
		} elseif ($pLayout) {
			$whereClause .= " tl.`layout`=? ORDER BY ";
			array_push($bindVars, $pLayout);
		}
		$query = "SELECT tl.`ord`, tl.`user_id`, tl.`layout`, tl.`layout_position`, tl.`params` AS `section_params`, tlm.*, tmm.`module_rsrc` FROM `" . BIT_DB_PREFIX . "themes_layouts` tl, `" . BIT_DB_PREFIX . "themes_layouts_modules` tlm, `" . BIT_DB_PREFIX . "themes_module_map` tmm
				WHERE tl.`module_id`=tlm.`module_id` AND tl.`user_id`=? AND tmm.`module_id`=tlm.`module_id` $whereClause  " . $this->mDb->convert_sortmode("ord_asc");
		if( $result = $this->mDb->query($query, $bindVars) ) {
			$row = $result->fetchRow();
			// CHeck to see if we have ACTIVE_PACKAGE modules at the top of the results
			if (isset($row['layout']) && ($row['layout'] != DEFAULT_PACKAGE) && (ACTIVE_PACKAGE != DEFAULT_PACKAGE)) {
				$skipDefaults = true;
			} else {
				$skipDefaults = false;
			}

			$gCenterPieces = array();
			while( $row ) {
				if ($skipDefaults && $row['layout'] == DEFAULT_PACKAGE) {
					// we're done! we've got all the non-DEFAULT_PACKAGE modules
					break;
				}

				if( !empty( $row["section_params"] ) ) {
					$row['params'] = $row['section_params'];
				}
				if( !empty( $row["groups"] ) ) {
					if( $this->isFeatureActive( 'site_show_all_modules_always' ) || $gBitUser->isAdmin() ) {
						$row["visible"] = TRUE;
					} else {
						if( preg_match( '/[A-Za-z]/', $row["groups"] ) ) {
							// old style serialized group names
							$row["module_groups"] = array();
							if( $grps = @unserialize($row["groups"]) ) {
								foreach ($grps as $grp) {
									global $gBitUser;
									if( !($groupId = array_search( $grp, $gBitUser->mGroups )) ) {
										if( $gBitUser->isAdmin() ) {
											$row["module_groups"][] = $gBitUser->groupExists( $grp, '*' );
										}
									}

									if( @$this->verifyId( $groupId ) ) {
										$row["module_groups"][] = $groupId;
									}
								}


							}
						} else {
							$row["module_groups"] = explode( ' ', $row["groups"] );
						}
						// Check for the right groups
						foreach( $row["module_groups"] as $modGroupId ) {
							if( $gBitUser->isInGroup( $modGroupId ) ) {
								$row["visible"] = TRUE;
								break; // no need to continue looping
							}
						}
					}
				} else {
					$row["visible"] = TRUE;
					$row["module_groups"] = array();
				}
				if (empty($ret[$row['layout_position']])) {
					$ret[$row['layout_position']] = array();
				}
				if ($row['layout_position'] == CENTER_COLUMN) {
					array_push($gCenterPieces, $row['module_rsrc']);
				}
				if (!empty($row["visible"])) {
					array_push($ret[$row['layout_position']], $row);
				}
				$row = $result->fetchRow();
			}
		}
		return $ret;
	}

	/*static*/
	function genPass() {
		$vocales = "aeiou";
		$consonantes = "bcdfghjklmnpqrstvwxyz123456789";
		$r = '';
		for ($i = 0; $i < 8; $i++) {
			if ($i % 2) {
				$r .= $vocales{rand(0, strlen($vocales) - 1)};
			} else {
				$r .= $consonantes{rand(0, strlen($consonantes) - 1)};
			}
		}
		return $r;
	}

	// === lookupMimeType
	/**
	* given an extension, return the mime type
	*
	* @param string $pExtension is the extension of the file or the complete file name
	* @return mime type of entry and populates $this->mMimeTypes with existing mime types
	* @access public
	*/
	function lookupMimeType( $pExtension ) {
		// rfc1341 - mime types are case insensitive.
		if( preg_match( "/.*\.[a-zA-Z]+$/", $pExtension ) ) {
			$pExtension = substr( $pExtension, ( strrpos( $pExtension, '.' ) + 1 )  );
		}
		$pExtension = strtolower( $pExtension );
		if( empty( $this->mMimeTypes ) ) {
			// use the local mime.types files if it is available since it may be more current
			$mimeFile = is_file( '/etc/mime.types' ) && is_readable( '/etc/mime.types' ) ? '/etc/mime.types' : KERNEL_PKG_PATH.'admin/mime.types';
			$this->mMimeTypes = array();
			if( $fp = fopen( $mimeFile,"r" ) ) {
				while( false != ($line = fgets( $fp, 4096 ) ) ) {
					if (!preg_match("/^\s*(?!#)\s*(\S+)\s+(?=\S)(.+)/",$line,$match)) {
						continue;
					}
					$tmp = preg_split( "/\s/",trim( $match[2] ) );
					foreach( $tmp as $type ) {
						$this->mMimeTypes[strtolower($type)] = $match[1];
					}
				}
				fclose( $fp );
			}
		}

		return( !empty( $this->mMimeTypes[$pExtension] ) ? $this->mMimeTypes[$pExtension] : 'application/binary' );
	}

	/**
	* * Return 'windows' if windows, otherwise 'unix'
	* \static
	*/
	function os()
	{
		static $os;
		if( !isset( $os ) ) {
			if( preg_match( "/WIN/",PHP_OS ) ) {
				$os = 'windows';
			} else {
				$os = 'unix';
			}
		}
		return $os;
	}


	/**
	* * Prepend $path to the include path
	* \static
	*/
	function prependIncludePath($path)
	{
		if(!function_exists("get_include_path"))
			include_once(UTIL_PKG_PATH . "PHP_Compat/Compat/Function/get_include_path.php");
		if(!defined("PATH_SEPARATOR"))
			include_once(UTIL_PKG_PATH . "PHP_Compat/Compat/Constant/PATH_SEPARATOR.php");
		if(!function_exists("set_include_path"))
			include_once(UTIL_PKG_PATH . "PHP_Compat/Compat/Function/set_include_path.php");

		$include_path = get_include_path();
		if ($include_path)
		{
			$include_path = $path . PATH_SEPARATOR . $include_path;
		}
		else
		{
			$include_path = $path;
		}
		return set_include_path($include_path);
	}

	/**
	* * Append $path to the include path
	* \static
	*/
	function appendIncludePath($path)
	{
		if(!function_exists("get_include_path"))
			include_once(UTIL_PKG_PATH . "PHP_Compat/Compat/Function/get_include_path.php");
		if(!defined("PATH_SEPARATOR"))
			include_once(UTIL_PKG_PATH . "PHP_Compat/Compat/Constant/PATH_SEPARATOR.php");
		if(!function_exists("set_include_path"))
			include_once(UTIL_PKG_PATH . "PHP_Compat/Compat/Function/set_include_path.php");

		$include_path = get_include_path();
		if ($include_path)
		{
			$include_path .= PATH_SEPARATOR . $path;
		}
		else
		{
			$include_path = $path;
		}
		return set_include_path($include_path);
	}

	/*!
		Check that everything is set up properly

		\static
	*/
	function checkEnvironment()
	{
		static $checked;
		global $gTempDirs;

		if ($checked)
		{
			return;
		}

		$errors = '';

		$docroot = BIT_ROOT_PATH;

		if (ini_get('session.save_handler') == 'files') {
			$save_path = ini_get('session.save_path');

			if (empty($save_path)) {
				$errors .= "The session.save_path variable is not setup correctly (its empty).\n";
			} else {
				if (strpos ($save_path, ";") !== FALSE) {
  					$save_path = substr ($save_path, strpos ($save_path, ";")+1);
				}
				$open = ini_get('open_basedir');
				if (!@is_dir($save_path) && empty($open)) {
					$errors .= "The directory '$save_path' does not exist or PHP is not allowed to access it (check open_basedir entry in php.ini).\n";
				} else if (!bw_is_writeable($save_path)) {
					$errors .= "The directory '$save_path' is not writeable.\n";
				}
			}

			if ($errors) {
				$save_path = getTempDir();

				if (is_dir($save_path) && bw_is_writeable($save_path)) {
					ini_set('session.save_path', $save_path);

					$errors = '';
				}
			}
		}

		$wwwuser = '';
		$wwwgroup = '';

		if (isWindows()) {
			if ( strpos($_SERVER["SERVER_SOFTWARE"],"IIS") && isset($_SERVER['COMPUTERNAME']) ) {
				$wwwuser = 'IUSR_'.$_SERVER['COMPUTERNAME'];
				$wwwgroup = 'IUSR_'.$_SERVER['COMPUTERNAME'];
			} else {
				$wwwuser = 'SYSTEM';
				$wwwgroup = 'SYSTEM';
			}
		}

		if (function_exists('posix_getuid')) {
			$userhash = @posix_getpwuid(@posix_getuid());

			$group = @posix_getpwuid(@posix_getgid());
			$wwwuser = $userhash ? $userhash['name'] : false;
			$wwwgroup = $group ? $group['name'] : false;
		}

		if (!$wwwuser) {
			$wwwuser = 'nobody (or the user account the web server is running under)';
		}

		if (!$wwwgroup) {
			$wwwgroup = 'nobody (or the group account the web server is running under)';
		}

		$permFiles[] = $this->getConfig( 'site_temp_dir', BIT_ROOT_PATH.'temp/' );

		foreach( $permFiles as $file ) {
			$present = FALSE;
			// Create directories as needed
			$target = $file;
			if( preg_match( '/.*\/$/', $target ) ) {
				// we have a directory
				if( !is_dir( $target ) ) {
					mkdir_p( $target, 02775 );
				}
			// Check again and report problems
				if( !is_dir( $target ) ) {
					if( !isWindows() ) {
						$errors .= "
							<p>The directory <strong style='color:red;'>$target</strong> does not exist. To create the directory, execute a command such as:</p>
							<pre>\$ mkdir -m 777 $target</pre>
						";
					} else {
						$errors .= "<p>The directory <strong style='color:red;'>$target</strong> does not exist. Create the directory $target before proceeding</p>";
					}
				} else {
					$present = TRUE;
				}
			} elseif( !file_exists( $target ) ) {
				if( !isWindows()) {
					$errors .= "<p>The file <b style='color:red;'>$target</b> does not exist. To create the file, execute a command such as:</p>
						<pre>
							\$ touch $target
							\$ chmod 777 $target
						</pre>
					";
				} else {
					$errors .= "<p>The file <b style='color:red;'>$target</b> does not exist. Create a blank file $target before proceeding</p>";
				}
			} else {
				$present = TRUE;
			}

			// chmod( $target, 02775 );
			if( $present && ( !bw_is_writeable( $target ) ) ) {
				if (!isWindows())
				{ $errors .= "<p><strong style='color:red;'>$target</strong> is not writeable by $wwwuser. To give $wwwuser write permission, execute a command such as:</p>
					<pre>\$ chmod 777 $target</pre>";
				} else {
					$errors .= "<p><b style='color:red;'>$target</b> is not writeable by $wwwuser. Check the security of the file $target before proceeding</p>";
				}
			}
			//if (!is_dir("$docroot/$dir")) {
			//	$errors .= "The directory '$docroot$dir' does not exist.\n";
			//} else if (!bw_is_writeable("$docroot/$dir")) {
			//	$errors .= "The directory '$docroot$dir' is not writeable by $wwwuser.\n";
			//}
		}

		if( $errors ) {
			$PHP_CONFIG_FILE_PATH = PHP_CONFIG_FILE_PATH;

			ob_start();
			phpinfo (INFO_MODULES);
			$httpd_conf = 'httpd.conf';

			if (preg_match('/Server Root<\/b><\/td><td\s+align="left">([^<]*)</', ob_get_contents(), $m)) {
				$httpd_conf = $m[1] . '/' . $httpd_conf;
			}

			ob_end_clean();

			print "
				<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">
				<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">
					<head>
						<title>bitweaver setup problems</title>
						<meta http-equiv=\"Pragma\" content=\"no-cache\" />
						<meta http-equiv=\"Expires\" content=\"-1\" />
					</head>
					<body>
						<h1 style=\"color:red;\">bitweaver is not properly set up:</h1>
						<blockquote>
							$errors
						</blockquote>
			";

			if( !defined( 'IS_LIVE' ) || !IS_LIVE ) {
				if (!isWindows()) {
					print "
						<p>Proceed to the installer <strong>at <a href=\"".BIT_ROOT_URL."install/install.php\">".BIT_ROOT_URL."install/install.php</a></strong> after you run the command.
						<br />Consult the bitweaver<a href='http://www.bitweaver.org/wiki/index.php?page=Technical_Documentation'>Technical Documentation</a> if you need more help.</p>
					";
				} else {
					print "
						<p>Proceed to the installer <strong>at <a href=\"".BIT_ROOT_URL."install/install.php\">".BIT_ROOT_URL."install/install.php</a></strong> after you have corrected the identified problems.
						<br />Consult the bitweaver<a href='http://www.bitweaver.org/wiki/index.php?page=Technical_Documentation'>Technical Documentation</a> if you need more help.</p>
					";
				}
				print "</body></html>";
			}

			exit;
		}

		$checked = true;
	}

	function pagination_url($find, $sort_mode, $name1 = "", $value1 = "", $name2 = "", $value2 = "") {
		$url = ($_SERVER['PHP_SELF']);
		$url .= "?find=$find&amp;sort_mode=$sort_mode";
		($name1) ? ($url .= "&amp;$name1=$value1") : ("");
		($name2) ? ($url .= "&amp;$name2=$value2") : ("");
		return $url;
	}

	//********************* CACHE METHODS **************************//

	function list_cache($offset, $max_records, $sort_mode, $find) {

		if ($find) {
		$findesc = '%' . $find . '%';

		$mid = " where (`url` like ?) ";
		$bindvars=array($findesc);
		} else {
		$mid = "";
		$bindvars=array();
		}

		$query = "select `cache_id` ,`url`,`refresh` from `".BIT_DB_PREFIX."liberty_link_cache` $mid order by ".$this->mDb->convert_sortmode($sort_mode);
		$query_cant = "select count(*) from `".BIT_DB_PREFIX."liberty_link_cache` $mid";
		$result = $this->mDb->query($query,$bindvars,$max_records,$offset);
		$cant = $this->mDb->getOne($query_cant,$bindvars);
		$ret = array();

		while ($res = $result->fetchRow()) {
		$ret[] = $res;
		}

		$retval = array();
		$retval["data"] = $ret;
		$retval["cant"] = $cant;
		return $retval;
	}

	function refresh_cache($cache_id) {
		global $gBitSystem;
		$query = "select `url`  from `".BIT_DB_PREFIX."liberty_link_cache`
		where `cache_id`=?";

		$url = $this->mDb->getOne($query, array( $cache_id ) );
		$data = tp_http_request($url);
		$refresh = $gBitSystem->getUTCTime();
		$query = "update `".BIT_DB_PREFIX."liberty_link_cache`
		set `data`=?, `refresh`=?
		where `cache_id`=? ";
		$result = $this->mDb->query($query, array( $data, $refresh, $cache_id) );
		return true;
	}

	function remove_cache($cache_id) {
		$query = "delete from `".BIT_DB_PREFIX."liberty_link_cache` where `cache_id`=?";

		$result = $this->mDb->query($query, array( $cache_id ) );
		return true;
	}

	function get_cache($cache_id) {
		$query = "select * from `".BIT_DB_PREFIX."liberty_link_cache`
		where `cache_id`=?";

		$result = $this->mDb->query($query, array( $cache_id ) );
		$res = $result->fetchRow();
		return $res;
	}

	//********************* DATE AND TIME METHODS **************************//

	/**
	 * Retrieve a current UTC timestamp
	 * Simple map to BitDate object allowing tidy display elsewhere
	 */
	function getUTCTime() {
		return	$this->mServerTimestamp->getUTCTime();
	}

	/**
	 * Retrieves the user's preferred offset for displaying dates.
	 */
	function get_display_offset($_user = false) {
		return $this->mServerTimestamp->get_display_offset($_user);
	}

	/**
	 * Retrieves the user's preferred long date format for displaying dates.
	 */
	function get_long_date_format() {
		static $site_long_date_format = false;

		if (!$site_long_date_format)
		$site_long_date_format = $this->getConfig('site_long_date_format', '%A %d of %B, %Y');

		return $site_long_date_format;
	}

	/**
	 * Retrieves the user's preferred short date format for displaying dates.
	 */
	function get_short_date_format() {
		static $site_short_date_format = false;

		if (!$site_short_date_format)
		$site_short_date_format = $this->getConfig('site_short_date_format', '%a %d of %b, %Y');

		return $site_short_date_format;
	}

	/**
	 * Retrieves the user's preferred long time format for displaying dates.
	 */
	function get_long_time_format() {
		static $site_long_time_format = false;

		if (!$site_long_time_format)
		$site_long_time_format = $this->getConfig('site_long_time_format', '%H:%M:%S %Z');

		return $site_long_time_format;
	}

	/**
	 * Retrieves the user's preferred short time format for displaying dates.
	 */
	function get_short_time_format() {
		static $site_short_time_format = false;

		if (!$site_short_time_format)
		$site_short_time_format = $this->getConfig('site_short_time_format', '%H:%M %Z');

		return $site_short_time_format;
	}

	/**
	 * Retrieves the user's preferred long date/time format for displaying dates.
	 */
	function get_long_datetime_format() {
		static $long_datetime_format = false;

		if (!$long_datetime_format)
		$long_datetime_format = $this->get_long_date_format(). ' [' . $this->get_long_time_format(). ']';

		return $long_datetime_format;
	}

	/**
	 * Retrieves the user's preferred short date/time format for displaying dates.
	 */
	function get_short_datetime_format() {
		static $short_datetime_format = false;

		if (!$short_datetime_format)
		$short_datetime_format = $this->get_short_date_format(). ' [' . $this->get_short_time_format(). ']';

		return $short_datetime_format;
	}

	/*
	 * Only used in rang_lib.php which needs tidying up to use smarty templates
	 */
	function get_long_datetime($timestamp, $user = false) {
		return $this->mServerTimestamp->strftime($this->get_long_datetime_format(), $timestamp, $user);
	}

	// Check for new version
	// returns an array with information on bitweaver version
	function checkBitVersion() {
		$local= BIT_MAJOR_VERSION.'.'.BIT_MINOR_VERSION.'.'.BIT_SUB_VERSION;
		$ret['local'] = $local;

		$error['number'] = 0;
		$error['string'] = $data = '';

		// cache the bitversion.txt file locally and update only once a day
		if( !is_file( TEMP_PKG_PATH.'bitversion.txt' ) || ( time() - filemtime( TEMP_PKG_PATH.'bitversion.txt' ) ) > 86400 ) {
			if( $h = fopen( TEMP_PKG_PATH.'bitversion.txt', 'w' ) ) {
				if( $fsock = @fsockopen( 'www.bitweaver.org', 80, $error['number'], $error['string'], 5 ) ) {
					@fwrite( $fsock, "GET /bitversion.txt HTTP/1.1\r\n" );
					@fwrite( $fsock, "HOST: www.bitweaver.org\r\n" );
					@fwrite( $fsock, "Connection: close\r\n\r\n" );

					$get_info = FALSE;
					while( !@feof( $fsock ) ) {
						if( $get_info ) {
							$data .= @fread( $fsock, 1024 );
						} else {
							if( @fgets( $fsock, 1024 ) == "\r\n" ) {
								$get_info = TRUE;
							}
						}
					}
					@fclose( $fsock );
				}
				fwrite( $h, $data );
				fclose( $h );
			}
		}

		if( is_readable( TEMP_PKG_PATH.'bitversion.txt' ) ) {
			$h = fopen( TEMP_PKG_PATH.'bitversion.txt', 'r' );
			if( isset( $h ) ) {
				$data = fread( $h, 1024 );
				fclose( $h );
			}

			// nuke all lines that don't just contain a version number
			$lines = explode( "\n", $data );
			foreach( $lines as $line ) {
				if( preg_match( "/^\d+\.\d+\.\d+$/", $line ) ) {
					$versions[] = $line;
				}
			}

			if( !empty( $data ) && !empty( $versions ) && preg_match( "/\d+\.\d+\.\d+/", $versions[0] ) ) {
				sort( $versions );
				foreach( $versions as $version ) {
					if( preg_match( "/^".BIT_MAJOR_VERSION."\./", $version ) ) {
						$ret['compare'] = version_compare( $local, $version );
						$ret['upgrade'] = $version;
						$ret['page'] = preg_replace( "/\.\d+$/", "", $version );
					}
				}
				// check if there have been any major releases
				$release = explode( '.', array_pop( $versions ) );
				if( $release[0] > BIT_MAJOR_VERSION ) {
					$ret['release'] = implode( '.', $release );
					$ret['page'] = $release[0].'.'.$release[1];
				} elseif( $release[0] < BIT_MAJOR_VERSION ) {
					$ret['compare'] = version_compare( $local, $version );
					$ret['upgrade'] = $version;
				}
			} else {
				$error['number'] = 1;
				$error['string'] = tra( 'No version information available. Check your connection to bitweaver.org' );
			}
		}
		// append any release level
		$ret['local'] .= ' '.BIT_LEVEL;
		$ret['error'] = $error;
		return $ret;
	}

	/**
	* Statically callable function to determine if the current call was made using Ajax
	*
	* @access public
	**/
	function isAjaxRequest() {
		return (!empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' ? true : false);
	}


	// should be moved somewhere else. unbreaking things for now - 25-JUN-2005 - spiderr
	// \TODO remove html hardcoded in diff2
	function diff2($page1, $page2) {
		$page1 = split("\n", $page1);
		$page2 = split("\n", $page2);
		$z = new WikiDiff($page1, $page2);
		if ($z->isEmpty()) {
		$html = '<hr /><br />[' . tra("Versions are identical"). ']<br /><br />';
		} else {
		//$fmt = new WikiDiffFormatter;
		$fmt = new WikiUnifiedDiffFormatter;
		$html = $fmt->format($z, $page1);
		}
		return $html;
	}
}


// === installError
/**
* If an unrecoverable error has occurred, this method should be invoked. script exist occurs
*
* @param string $ pMsg error message to be displayed
* @return none this function will DIE DIE DIE!!!
* @access public
*/
function installError($pMsg = null)
{
	global $gBitDbType;
	// here we decide where to go. if there are no db settings yet, we go the welcome page.
	if( isset( $gBitDbType ) ) {
		$step = 1;
	} else {
		$step = 0;
	}

	header( "Location: http://".$_SERVER['HTTP_HOST'].BIT_ROOT_URL."install/install.php?step=".$step );
/*	// figure out our subdirectories, if any.
	echo '<html><head><meta http-equiv="pragma" content="no-cache"><meta http-equiv="expires" content="1" /></head><body>';
	echo "<p>$pMsg</p>";
	echo "<p>Run <a href=\"" . BIT_ROOT_URL . "install/install.php\">install/install.php</a> to begin the installation process.</p>";
	echo "</body></html>";*/
	die;
}

/**
 * Basic processes timer
 *
 * @package kernel
 */
class BitTimer
{
	function parseMicro($micro)
	{
		list($micro, $sec) = explode(' ', microtime());

		return $sec + $micro;
	}

	function start($timer = 'default')
	{
		$this->mTimer[$timer] = $this->parseMicro(microtime());
	}

	function stop($timer = 'default')
	{
		return $this->current($timer);
	}

	function elapsed($timer = 'default')
	{
		return $this->parseMicro(microtime()) - $this->mTimer[$timer];
	}
}

global $gBitTimer;
$gBitTimer = new BitTimer();
$gBitTimer->start();

/* \brief  substr with a utf8 string - works only with $start and $length positive or nuls
* This function is the same as substr but works with multibyte
* In a multybyte sequence, the first byte of a multibyte sequence that represents a non-ASCII character is always in the range 0xC0 to 0xFD
* and it indicates how many bytes follow for this character.
* All further bytes in a multibyte sequence are in the range 0x80 to 0xBF.
*/
/**
 * Check mb_substr availability
 */
if (function_exists('mb_substr'))
{
	mb_internal_encoding("UTF-8");
}
else
{
	function mb_substr($str, $start, $len = '', $encoding = "UTF-8")
	{
		$limit = strlen($str);
		for ($s = 0; $start > 0;--$start) // found the real start
		{
			if ($s >= $limit)
				break;
			if ($str[$s] <= "\x7F")
				++$s;
			else
			{
				++$s; // skip length
				while ($str[$s] >= "\x80" && $str[$s] <= "\xBF")
				++$s;
			}
		}
		if ($len == '')
			return substr($str, $s);
		else
			for ($e = $s; $len > 0; --$len) // found the real end
		{
			if ($e >= $limit)
				break;
			if ($str[$e] <= "\x7F")
				++$e;
			else
			{
				++$e; //skip length
				while ($str[$e] >= "\x80" && $str[$e] <= "\xBF" && $e < $limit)
				++$e;
			}
		}
		return substr($str, $s, $e - $s);
	}
}


?>
